package com.hqd.ch03.v48.boot.context.config;

import com.hqd.ch03.v48.boot.util.Instantiator;
import com.hqd.ch03.v48.core.ResolvableType;
import com.hqd.ch03.v48.io.support.SpringFactoriesLoader;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ConfigDataLoaders {


    private final List<ConfigDataLoader<?>> loaders;

    private final List<Class<?>> resourceTypes;

    public ConfigDataLoaders(ClassLoader classLoader) {
        this(classLoader, SpringFactoriesLoader.loadFactoryNames(ConfigDataLoader.class, classLoader));
    }

    ConfigDataLoaders(
            ClassLoader classLoader, List<String> names) {
        Instantiator<ConfigDataLoader<?>> instantiator = new Instantiator<>(ConfigDataLoader.class,
                (availableParameters) -> {
                });
        this.loaders = instantiator.instantiate(classLoader, names);
        this.resourceTypes = getResourceTypes(this.loaders);
    }

    private List<Class<?>> getResourceTypes(List<ConfigDataLoader<?>> loaders) {
        List<Class<?>> resourceTypes = new ArrayList<>(loaders.size());
        for (ConfigDataLoader<?> loader : loaders) {
            resourceTypes.add(getResourceType(loader));
        }
        return Collections.unmodifiableList(resourceTypes);
    }

    private Class<?> getResourceType(ConfigDataLoader<?> loader) {
        return ResolvableType.forClass(loader.getClass()).as(ConfigDataLoader.class).resolveGeneric();
    }

    /**
     * Load {@link ConfigData} using the first appropriate {@link ConfigDataLoader}.
     *
     * @param <R>      the resource type
     * @param context  the loader context
     * @param resource the resource to load
     * @return the loaded {@link ConfigData}
     * @throws IOException on IO error
     */
    <R extends ConfigDataResource> ConfigData load(ConfigDataLoaderContext context, R resource) throws IOException {
        ConfigDataLoader<R> loader = getLoader(context, resource);
        return loader.load(context, resource);
    }

    @SuppressWarnings("unchecked")
    private <R extends ConfigDataResource> ConfigDataLoader<R> getLoader(ConfigDataLoaderContext context, R resource) {
        ConfigDataLoader<R> result = null;
        for (int i = 0; i < this.loaders.size(); i++) {
            ConfigDataLoader<?> candidate = this.loaders.get(i);
            if (this.resourceTypes.get(i).isInstance(resource)) {
                ConfigDataLoader<R> loader = (ConfigDataLoader<R>) candidate;
                if (loader.isLoadable(context, resource)) {
                    if (result != null) {
                        throw new IllegalStateException("Multiple loaders found for resource '" + resource + "' ["
                                + candidate.getClass().getName() + "," + result.getClass().getName() + "]");
                    }
                    result = loader;
                }
            }
        }
        return result;
    }

}

